/*
===========================================================================
This source file is part of DyLab (Dynamic Laboratory)
For the latest info, see http://dylab.googlecode.com

Copyright (c) 2006-2008 Lukas Krejci
(krejci.lukas@volny.cz)

This file is part of DyLab.

    DyLab is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    DyLab is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with DyLab.  If not, see <http://www.gnu.org/licenses/>.
===========================================================================
*/

//===========================================================================================
#include <dylab/simulation/Simulation.h>
#include <dylab/simobject/masspoints/MassPointsStructureBase.h>
#include <dylab/utils/log/Log.h>

// ============================================================================================================================
void dylab::Simulation::registerAllComponentTypes()
{
	ComGfx::registerComponentType("Gfx");
	ComInput::registerComponentType("Input");
	ComHaptics::registerComponentType("Haptics");
	ComCollisionHandler::registerComponentType("CollisionHandler");
	ComEventListener::registerComponentType("EventListener");
}
//-----------------------------------------------------------------------------
void dylab::Simulation::registerAllEntities()
{
	Simulation::registerEntity("simulation");
	ComCollisionHandler::registerEntity("collHandler");
	ComEventListener::registerEntity("listener");
	ComInput::registerEntity("input");
	ComHaptics::registerEntity("haptics");
	ComGfx::registerEntity("gfx");
}

//===========================================================================================
dylab::Simulation::Simulation()
: mQuitFlag(false), mSimObjectsPaused(false), mEventListeners(true), mCollisionHandlers(true)
, mElapsedTime(DYLAB_R(0)), mResetTimer(true), mTimeStep(DYLAB_R(0.004)), mTimeFactor(DYLAB_R(1))
, mGlobalForce(Vector3(0, -10, 0))
{
}
//-----------------------------------------------------------------------------
dylab::Simulation::~Simulation()
{
    // destroy all simulation objects
    mSimObjects.removeAll();
    
    // destroy all collision handlers
    mCollisionHandlers.removeAll();

    // destroy all events listeners
    mEventListeners.removeAll();

    // clean simulation input event dispatcher
    mInputEventDispatcher.unregisterAllListeners();
    mInputEventDispatcher.clearEventQueue();

    // clean simulation gfx event dispatcher
    mGfxEventDispatcher.unregisterAllListeners();
    mGfxEventDispatcher.clearEventQueue();

    // destroy simulation input component
    mInput.destroy();

    // destroy simulation gfx component
    mGfx.destroy();

    // destroy simulation haptics component
    mHaptics.destroy();
}
//-----------------------------------------------------------------------------
void dylab::Simulation::loadFromDefinition(const String & _defName)
{ 
    // retrieve the definition property tree
	PropertyTree & pt = getEntityDefinition(_defName)->getPropertyTree();

    // apply the loaded properties
    setGlobalForce(pt.getNodeValueT("globalForce", getGlobalForce()));
    setTimeStep(pt.getNodeValueT("timeStep", getTimeStep()));
    setTimeFactor(pt.getNodeValueT("timeFactor", getTimeFactor()));

    // create & load gfx
    PropertyTree & gfxPT = pt.getNode("gfx")->getChildren();
	ComGfx::loadComFromPropertyTree(createGfx(IComponent::loadComName(gfxPT)), gfxPT);

    // create & load input	
	PropertyTree & inputPT = pt.getNode("input")->getChildren();
	ComInput::loadComFromPropertyTree(createInput(IComponent::loadComName(inputPT)), inputPT);

    // create & load haptics
	PropertyTree & hapticsPT = pt.getNode("haptics")->getChildren();	
	ComHaptics::loadComFromPropertyTree(createHaptics(IComponent::loadComName(hapticsPT)), hapticsPT);

    // create & load all event listeners
    for (PropertyTree::NodeList::Iterator listener(*pt.getNodeList("listener")); listener.isValid(); listener++)                          
	{ DYLAB_TRACEF("Creating & loading listener: \"%1%\"", listener->getValue());
		ComEventListener::loadComFromPropertyTree(createEventListener(listener->getValue(), 
			IComponent::loadComName(listener->getChildren())), listener->getChildren());    
		DYLAB_TRACE("Done creating & loading listener");
	}

    // create & load collision handlers
    if (pt.getNodeCount("collHandler") > 0)
    {
        for (PropertyTree::NodeList::Iterator handler(*pt.getNodeList("collHandler")); handler.isValid(); handler++)		
			ComCollisionHandler::loadComFromPropertyTree(createCollisionHandler(handler->getValue(), 
				IComponent::loadComName(handler->getChildren())), handler->getChildren());					
    }

    // create & load all simulation objects
    for (PropertyTree::NodeList::Iterator sim_object(*pt.getNodeList("simObject")); sim_object.isValid(); sim_object++)    
        createSimObject(sim_object->getValue())->loadFromPropertyTree(sim_object->getChildren());
}
//-----------------------------------------------------------------------------
dylab::ComGfx::Instance * dylab::Simulation::createGfx(const String & _simGfxName)
{
    // create the specified simulation gfx
    mGfx = ComGfx::create(_simGfxName);

    // initialize the created component
    ComGfx::ScopedExLock(&mGfx)->initialize(this);

    // return the ready-to-use component instance
    return getGfx();
}
//-----------------------------------------------------------------------------
dylab::ComInput::Instance * dylab::Simulation::createInput(const String & _simInputName)
{
    // create the specified simulation input
	mInput = ComInput::create(_simInputName);

    // initialize the created component
    ComInput::ScopedExLock(&mInput)->initialize(this);

    // return the ready-to-use component instance
    return getInput();
}
//-----------------------------------------------------------------------------
dylab::ComHaptics::Instance * dylab::Simulation::createHaptics(const String & _simHapticsName)
{
    // create the specified simulation input
    mHaptics = ComHaptics::create(_simHapticsName);

    // initialize the created component
    ComHaptics::ScopedExLock(&mHaptics)->initialize(this);

    // return the ready-to-use component instance
    return getHaptics();
}
//-----------------------------------------------------------------------------
dylab::ComEventListener::Instance * 
dylab::Simulation::createEventListener(const String & _listenerName, const String & _listenerComName)
{ 
    // create event listener component
    ComEventListener::Instance * listener = new ComEventListener::Instance(ComEventListener::create(_listenerComName));
    DYLAB_ASSERT(listener != NULL);

    // insert the listener into the register under the specified name
    mEventListeners.insert(_listenerName, listener); 

    // initialize the listener
    ComEventListener * list = ComEventListener::ScopedExLock(listener);
	list->setNameInSimulation(_listenerName);
	list->initialize(this);

    // return the created listener
    return listener;
}
//-----------------------------------------------------------------------------
void dylab::Simulation::destroyEventListener(const String & _listenerName)
{
    // destroy the event listener
    mEventListeners.remove(_listenerName);
}
//-----------------------------------------------------------------------------
dylab::SimObject * dylab::Simulation::createSimObject(const String & _simObjectName)
{ 
    // create new simulation object
    SimObject * simObj = new SimObject(this, _simObjectName);
    DYLAB_ASSERT(simObj != NULL);

    // store it in the container
    mSimObjects.insert(_simObjectName, simObj); 

    // return the newly created simulation object
    return simObj;
}
//-----------------------------------------------------------------------------
void dylab::Simulation::destroySimObject(const String & _simObjectName)
{ 
    // destroy the simulation object named _simObjectName
    mSimObjects.remove(_simObjectName); 
}
//-----------------------------------------------------------------------------
dylab::ComCollisionHandler::Instance * 
dylab::Simulation::createCollisionHandler(const String & _handlerName, const String & _handlerComName)
{ 
    // create collision handler component
    ComCollisionHandler::Instance * handler = new ComCollisionHandler::Instance(ComCollisionHandler::create(_handlerComName));
    DYLAB_ASSERT(handler != NULL);

    // insert the handler into the register under the specified name
    mCollisionHandlers.insert(_handlerName, handler); 

    // initialize the handler
    ComCollisionHandler::ScopedExLock(handler)->initialize(this);

    // return the created handler
    return handler;
}
//-----------------------------------------------------------------------------
void dylab::Simulation::destroyCollisionHandler(const String & _handlerName)
{
    // destroy the collision handler
    mCollisionHandlers.remove(_handlerName);
}
//-----------------------------------------------------------------------------
void dylab::Simulation::logStatistics()
{
    DYLAB_LOG("*** Simulation statistics start *** ");

	// log simulation info
	DYLAB_LOGF2("timeStep=%1%, timeFactor=%2%", mTimeStep, mTimeFactor);

    // log gfx description
    DYLAB_LOG(ComGfx::ScopedShLock(&mGfx)->getDescription());

    // log input description
    DYLAB_LOG(ComInput::ScopedShLock(&mInput)->getDescription());

    // log haptics description
    DYLAB_LOG(ComHaptics::ScopedShLock(&mHaptics)->getDescription());

    // log event listeners
    for (EventListeners::Iterator listener(mEventListeners); listener.isValid(); listener++)
        DYLAB_LOG(ComEventListener::ScopedShLock(listener())->getDescription());

    // log collision handlers
    for (CollisionHandlers::Iterator handler(mCollisionHandlers); handler.isValid(); handler++)
        DYLAB_LOG(ComCollisionHandler::ScopedShLock(handler())->getDescription());

    // log simulation objects statistics
    for (SimObjects::Iterator simObject(mSimObjects); simObject.isValid(); simObject++)
        simObject->logStatistics();    

    DYLAB_LOG("*** Simulation statistics end *** ");
}
//-----------------------------------------------------------------------------
void dylab::Simulation::updateSimObjects(real_t * _techniqueTime, real_t * _integratorTime, real_t * _colhandlTime)
{
    // update simulation objects in timespace, if not paused
    if (mSimObjectsPaused)
        return;

    // when first calling, only restart the timer
    if (mResetTimer) {
        mResetTimer = false;
        mElapsedTime = DYLAB_R(0);
        mTimer.reset();        
        return;
    }

    // store the max time step value
    real_t timeStep = mTimeStep;

    // store elapsed time since the last call (i.e. timer reset) and reset timer
    mElapsedTime += mTimer.getElapsedSeconds() * mTimeFactor;
    
    // no time, so there is nothing to update
    if (mElapsedTime < timeStep)
        return;

    // reset the simulation timer
    mTimer.reset();

    while (mElapsedTime >= timeStep)
    {
        // update step
        _updateAllSimObjects(timeStep, _techniqueTime, _integratorTime);

        // collision step
        _handleAllCollisions(_colhandlTime);

        // part of the time has been consumed
        mElapsedTime -= timeStep;
    }
}
//-----------------------------------------------------------------------------
void dylab::Simulation::pauseSimObjects(bool _pause)
{
    // when resuming paused simulation objects, reset their timers
    if (!_pause && mSimObjectsPaused)
        mResetTimer = true;

    // set the paused flag
    mSimObjectsPaused = _pause;
}
//-----------------------------------------------------------------------------
void dylab::Simulation::renderFrame(real_t * _renderingTime)
{
    // for statistics
    Timer timer;

    // render one frame of the default scene
    ComGfx::ScopedExLock(&mGfx)->renderScene();

    // gather statistics info
    if (_renderingTime != NULL)
        *_renderingTime += timer.getElapsedMiliseconds();
}
//-----------------------------------------------------------------------------
void dylab::Simulation::captureInput(real_t * _inputTime)
{
    // for statistics
    Timer timer;

    // capture simulation input    
    ComInput::ScopedExLock(&mInput)->capture();     

    // gather statistics info
    if (_inputTime != NULL)
        *_inputTime += timer.getElapsedMiliseconds();
}
//-----------------------------------------------------------------------------
void dylab::Simulation::updateHaptics(real_t * _hapticsTime)
{
    // for statistics
    Timer timer;

    // capture simulation haptics    
    ComHaptics::ScopedExLock(&mHaptics)->updateAllDevices();

    // gather statistics info
    if (_hapticsTime != NULL)
        *_hapticsTime += timer.getElapsedMiliseconds();
}
//-----------------------------------------------------------------------------
void dylab::Simulation::dispatchAllEvents(real_t * _eventsTime)
{ 
    // for statistics
    Timer timer;

    // dispatch all gfx events
    while (mGfxEventDispatcher.dispatchOneEvent());

    // dispatch all input events    
    while (mInputEventDispatcher.dispatchOneEvent()); 

    // dispatch all haptics events    
    while (mHapticsEventDispatcher.dispatchOneEvent()); 

    // gather statistics info
    if (_eventsTime != NULL)
        *_eventsTime += timer.getElapsedMiliseconds();
}
//-----------------------------------------------------------------------------
void dylab::Simulation::_updateAllSimObjects(real_t _timeStep, real_t * _techniqueTime, real_t * _integratorTime)
{
    //TODO: use thread pool to run sim objects update in parallel
    //TODO: mSimObjectsUpdater.execute(); (its type is ThreadPool::Mission)

    for (SimObjects::Iterator simObject(mSimObjects); simObject.isValid(); simObject++)
    {        
        {
            // lock the model and retrieve model structure
            ComModelStructure::ScopedExLock model(simObject->getModelStructure());

			// see if this simulation object supports mass points inteface
			if (MassPointsStructureBase::isImplementedIn(model))
			{
	            // add the global force to all mass points of this simulation object
		        for (MassPointContainer::Iterator mp(MassPointsStructureBase::getImplFrom(model)->massPointIterator()); 
						mp.isValid(); mp++)    
			        mp->currState().force += getGlobalForce();
			}
        }

		// update the simulation object
        simObject->update(_timeStep, _techniqueTime, _integratorTime);
    }
}
//-----------------------------------------------------------------------------
void dylab::Simulation::_handleAllCollisions(real_t * _colhandlTime)
{
    // for statistics
    Timer timer;

    // handle collisions
    for (CollisionHandlers::Iterator handler(mCollisionHandlers); handler.isValid(); handler++)
        ComCollisionHandler::ScopedExLock(handler())->handleCollisions();

    // gather statistics info
    if (_colhandlTime != NULL)
        *_colhandlTime += timer.getElapsedMiliseconds();
}
//-----------------------------------------------------------------------------